CloudFormation 一撃で EC2 の Blue/Green Deployment の CodePipeline を構築する
はじめに
おはようございます、もきゅりんです。
皆さん、Blue/Green (以下B/G)デプロイメントしてますか?
B/G デプロイメントとは、はすでに理解されていることを前提として話を進めます。
今さらなのですが EC2 の B/G デプロイを実行する CodePipeline を構築する機会があったのでまとめておきます。
CloudFormation の CodeDeploy デプロイメントグループは現在(2021/4/29)、EC2 AutoScaling の対応をしていないため、カスタムリソースで構築します。
AWS::CodeDeploy::DeploymentGroup - AWS CloudFormation
ブルー/グリーンデプロイの場合、AWSCloudFormationはLambdaコンピューティングプラットフォームでのデプロイのみをサポートします。AWS::CodeDeploy::BlueGreen フックを使用して、ECSのBlue/Greenの展開を実行できます。
カスタムリソースとは
カスタムリソースとは?という人は カスタムリソース - AWS CloudFormation をご参照下さい。
カスタムリソースを使用すると、テンプレートにカスタムのプロビジョニングロジックを記述し、ユーザーがスタックを作成、更新(カスタムリソースを変更した場合)、削除するたびに AWS CloudFormation がそれを実行します。たとえば、AWS CloudFormation のリソースタイプとして使用できないリソースを含める必要があるとします。それらのリソースは、カスタム リソースを使用して含めることができます。この方法により、すべての関連リソースを 1 つのスタックで管理できます。
本稿では、AWS Lambda-backed カスタムリソース - AWS CloudFormation を使って、AWS CloudFormation とLambda 関数との組み合わせで環境を構築します。
EC2 の Blue Green デプロイメントについて
EC2のB/Gデプロイでは、CodeDeployを利用して、自動切り替えと手動による切り替えと、AutoScalingGroup(以下 ASG)自動コピーによる切り替えとASG、タグベースによるEC2、オンプレの任意組み合わせによる切り替えの2つがあります。
手動切り替えの場合、CodeDeploy のデプロイメントグループにてリプレース対象の指定、デプロイ設定でデプロイ先のリソースを指定します。
まとめると以下表のようになります。
実行方法 | パターン1 | パターン2 | パターン3 |
---|---|---|---|
自動 | ASG自動コピー | なし | なし |
手動 | ASG切り替え | 非ASG (EC2 with Tag) | オンプレミス |
手動実行のフローはCodeDeploy で非 AutoScaling のインスタンスへの Blue/Green デプロイを試してみた | DevelopersIO の図を拝借すると以下のように実行されます。
事前に置き換え後のリソースを作成しておく必要があります。
なお、CodePipelineのように自動でデプロイを実行させる場合は ASG のコピーが必須です。
下図のようにデプロイ先のリソースが存在しないというエラーが発生します。
自動実行のフローはCodeDeploy のデプロイ方式に Blue/Green Deployment が追加されました | DevelopersIO の図を拝借すると以下のように実行されます。
自動、手動のどちらの場合でも、仮にロールバックした後は新規作成したリソースを手動で削除する必要があります。
結果のイメージ
元の状態
リプレースされた状態
構成図
今回構成する図は以下です。
本来は ALB が配置されるパブリックサブネットとインスタンスが配置されるプライベートサブネットとで分離するのが自然ですが、本稿ではインスタンスもALBと同じパブリックサブネットに配置することを想定しています。
前提条件
- VPC, サブネット, セキュリティグループが作成済み, インターネット通信可能であること
- AutoScalingで利用するためのAMIが作成済み、CodeDeploy エージェントのインストール がされていること
-
CodeCommitが作成済み
スタック作成時に以下パラメーターの入力が必須です。
(その他デフォルト指定しているパラメータもあります)
パラメーター名 | 説明 |
---|---|
VpcId | 構築に使用するVPC ID |
AlbSecurityGroupId | ALBで使用するセキュリティグループID |
AlbSubnetId1 | ALBで使用するサブネットID |
AlbSubnetId2 | ALBで使用するサブネットID |
LaunchConfigImageId | Launch Configに指定するAMI ID |
Ec2KeyPair | EC2で使用するキーペアを指定 |
AppSecurityGroupId | EC2で使用するセキュリティグループID |
AppSubnetId1 | EC2が配置されるサブネットID(ALBと同じサブネットを使用します) |
AppSubnetId2 | EC2が配置されるサブネットID(ALBと同じサブネットを使用します) |
CodeCommitRepositoryName | 使用するCodeCommitリポジトリの名前 |
準備
CodeCommitに以下をプッシュします。
なお、CodePipelineによる自動デプロイではファイル上書きデプロイを設定できないので、必要に応じて appspec.ymlで元のファイルを削除するように対応します。
- ソースコード(index.html, hello.conf)
- appspec.yml
- (本稿では beforeInstall.sh を利用)
ちなみに、index.html や hello.conf の素材は こちら を使っています。
参考
## appspec.yml version: 0.0 os: linux files: - source: ./hello.conf destination: /etc/nginx/conf.d/ - source: ./index.html destination: /usr/share/nginx/html/ hooks: BeforeInstall: - location: ./beforeInstall.sh
## beforeInstall.sh #!/bin/sh if [ -e /usr/share/nginx/html/index.html ]; then rm /usr/share/nginx/html/index.html fi if [ -e /etc/nginx/conf.d/hello.conf ]; then rm /etc/nginx/conf.d/hello.conf fi
Cloudformationテンプレート
注記
Lambda 関数は Python で記載しているので、必要に応じて CodeDeploy — Boto3 Docs 1.17.59 documentation を確認の上、パラメータを微調整してご利用頂ければと思います。
CodeDeployのデプロイメントグループのデプロイ設定は下記です。
- トラフィックを再ルーティングさせるのは5分後です。
- デプロイ設定は CodeDeployDefault.AllAtOnce です。
- 元インスタンスは15分後に削除します。
- デプロイが失敗したらロールバックします。
Working with deployment configurations in CodeDeploy - AWS CodeDeploy
このパイプライン上の Build ステージでは 何もしていません。ご利用する環境や言語に応じて、適切なビルドやテストを導入するようにして下さい。
CodeBuild のビルド仕様に関するリファレンス - AWS CodeBuild
承認ステージはコメントアウトしています。必要に応じて SNS Topic Arn をパラメータに設定して利用して下さい。
AWSTemplateFormatVersion: '2010-09-09' Description: AutoScaling Group and CodeDeploy for Blue/Green Pipeline Create. # ------------------------------------------------------------# # Input Parameters # ------------------------------------------------------------# Parameters: ProjectName: Default: 'demo' Type: String VpcId: Description: 'VPC ID' Type: AWS::EC2::VPC::Id AlbSecurityGroupId: Type: AWS::EC2::SecurityGroup::Id AlbSubnetId1: Description: 'Alb Subnet 1st' Type: AWS::EC2::Subnet::Id AlbSubnetId2: Description: 'Alb Subnet 2st' Type: AWS::EC2::Subnet::Id LaunchConfigImageId: Description: 'Enter ami-id' Type: AWS::EC2::Image::Id Ec2KeyPair: Description: 'Key Pair for EC2' Type: AWS::EC2::KeyPair::KeyName InstanceType: Description: Enter InstanceType Type: String Default: 't2.micro' AppSecurityGroupId: Type: AWS::EC2::SecurityGroup::Id #PublicSubnet1 AppSubnetId1: Description: 'App Subnet 1st' Type: AWS::EC2::Subnet::Id #PublicSubnet2 AppSubnetId2: Description: 'App Subnet 2st' Type: AWS::EC2::Subnet::Id InternetAlbName: Type: String Default: 'Alb' TargetGroupName: Type: String Default: 'tg' CodeDeployAppName: Type: String Default: 'app' CodeDeployDeploymentGroupName: Type: String Default: 'dg' DeploymentConfigName: Type: String Default: 'CodeDeployDefault.AllAtOnce' CodeCommitRepositoryName: Type: String BranchName: Type: String Default: 'master' # SNS Topic Arn # NotificationArn: # Type: String Resources: # ------------------------------------------------------------# # Target Group # ------------------------------------------------------------# TargetGroup: Type: AWS::ElasticLoadBalancingV2::TargetGroup Properties: VpcId: !Ref VpcId Name: !Sub ${ProjectName}-${TargetGroupName} Protocol: HTTP HealthCheckPath: '/' Port: 80 TargetType: instance # ------------------------------------------------------------# # Internet Alb # ------------------------------------------------------------# InternetAlb: Type: AWS::ElasticLoadBalancingV2::LoadBalancer Properties: Name: !Sub ${ProjectName}-${InternetAlbName} Tags: - Key: Name Value: !Sub ${ProjectName}-${InternetAlbName} Scheme: internet-facing LoadBalancerAttributes: - Key: 'deletion_protection.enabled' Value: 'false' - Key: 'idle_timeout.timeout_seconds' Value: '60' - Key: 'access_logs.s3.enabled' Value: 'true' - Key: 'access_logs.s3.bucket' Value: !Sub 'alb-log-${AWS::AccountId}' SecurityGroups: - !Ref AlbSecurityGroupId Subnets: - !Ref AlbSubnetId1 - !Ref AlbSubnetId2 AlbListener: Type: 'AWS::ElasticLoadBalancingV2::Listener' Properties: DefaultActions: - TargetGroupArn: !Ref TargetGroup Type: forward LoadBalancerArn: !Ref InternetAlb Port: 80 Protocol: HTTP # ------------------------------------------------------------# # Iam Roles # ------------------------------------------------------------# # EC2に適用するIAMRole Ec2IamRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: 'Allow' Principal: Service: - 'ec2.amazonaws.com' Action: - 'sts:AssumeRole' Path: '/' ManagedPolicyArns: - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore Policies: - PolicyName: !Sub ${ProjectName}-CodeDeployForEC2Policy PolicyDocument: Version: 2012-10-17 Statement: - Resource: - !Sub arn:aws:s3:::${ArtifactBucket}/* Effect: 'Allow' Action: - 's3:Get*' - 's3:List*' RoleName: !Sub ${ProjectName}-ec2-role IamInstanceProfile: Type: AWS::IAM::InstanceProfile Properties: Path: '/' Roles: - !Ref Ec2IamRole # CodeDeployに適用するIAMRole CodeDeployRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: 'Allow' Principal: Service: - 'codedeploy.amazonaws.com' Action: - 'sts:AssumeRole' Path: '/' ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSCodeDeployRole RoleName: !Sub ${ProjectName}-codedeploy-role # CodePipelineに適用するIAMRole CodePipelineServiceRole: Type: AWS::IAM::Role Properties: RoleName: !Sub ${ProjectName}-${CodeDeployAppName}-CodePipelineServiceRole Path: / AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: codepipeline.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: !Sub ${ProjectName}-${CodeDeployAppName}-CodePipelineServicePolicy PolicyDocument: Version: 2012-10-17 Statement: - Action: - iam:PassRole Resource: '*' Effect: Allow Condition: StringEqualsIfExists: iam:PassedToService: - ec2.amazonaws.com - Resource: - !Sub arn:aws:s3:::${ArtifactBucket}/* Effect: Allow Action: - s3:PutObject - s3:GetObject - s3:GetObjectVersion - s3:GetBucketVersioning - Action: - codecommit:CancelUploadArchive - codecommit:GetBranch - codecommit:GetCommit - codecommit:GetRepository - codecommit:GetUploadArchiveStatus - codecommit:UploadArchive Resource: '*' Effect: Allow - Action: - codedeploy:CreateDeployment - codedeploy:GetApplication - codedeploy:GetApplicationRevision - codedeploy:GetDeployment - codedeploy:GetDeploymentConfig - codedeploy:RegisterApplicationRevision - codedeploy:* Resource: '*' Effect: Allow - Action: - ec2:* - elasticloadbalancing:* - autoscaling:* - cloudwatch:* - sns:* - cloudformation:* Resource: '*' Effect: Allow - Action: - codebuild:BatchGetBuilds - codebuild:StartBuild - codebuild:BatchGetBuildBatches - codebuild:StartBuildBatch Resource: '*' Effect: Allow # CodeWatchEventを実行するIAMRole CloudwatchEventRole: Type: AWS::IAM::Role Properties: RoleName: !Sub ${ProjectName}-${CodeDeployAppName}-CloudWatchEventRole AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - events.amazonaws.com Action: sts:AssumeRole Path: / Policies: - PolicyName: CloudWatchEventsPipelineExecution PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: codepipeline:StartPipelineExecution Resource: !Sub arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:${Pipeline} # CodeBuildに適用するIAMRole CodeBuildServiceRole: Type: AWS::IAM::Role Properties: RoleName: !Sub ${ProjectName}-${CodeDeployAppName}-CodeBuildServiceRole Path: / AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: codebuild.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: !Sub ${ProjectName}-${CodeDeployAppName}-CodeBuildServicePolicy PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Resource: '*' Action: - logs:CreateLogGroup - logs:CreateLogStream - logs:PutLogEvents - Effect: Allow Resource: !Sub arn:aws:s3:::${ArtifactBucket}/* Action: - s3:PutObject - s3:GetObject - s3:GetObjectVersion - s3:GetBucketAcl - s3:GetBucketLocation - Effect: Allow Action: - codebuild:CreateReportGroup - codebuild:CreateReport - codebuild:UpdateReport Resource: '*' # ------------------------------------------------------------# # Auto Scaling Service # ------------------------------------------------------------# LaunchConfiguration: Type: AWS::AutoScaling::LaunchConfiguration Properties: AssociatePublicIpAddress: true IamInstanceProfile: !Ref IamInstanceProfile ImageId: !Ref LaunchConfigImageId InstanceMonitoring: false InstanceType: !Ref InstanceType KeyName: !Ref Ec2KeyPair LaunchConfigurationName: !Sub ${ProjectName}-lcg SecurityGroups: - !Ref AppSecurityGroupId AutoScalingGroup: Type: AWS::AutoScaling::AutoScalingGroup Properties: AutoScalingGroupName: !Sub ${ProjectName}-asg LaunchConfigurationName: !Ref LaunchConfiguration DesiredCapacity: '1' MaxSize: '1' MinSize: '1' Tags: - Key: Name Value: !Sub ${ProjectName}-asg-ec2 PropagateAtLaunch: true TargetGroupARNs: - !Ref TargetGroup VPCZoneIdentifier: - !Ref AppSubnetId1 - !Ref AppSubnetId2 # ------------------------------------------------------------# # Lambda # ------------------------------------------------------------# LambdaFunction: Type: 'AWS::Lambda::Function' DeletionPolicy: 'Delete' Properties: Code: ZipFile: | import boto3 import json import logging import cfnresponse from botocore.exceptions import ClientError logger = logging.getLogger() logger.setLevel(logging.INFO) client = boto3.client('codedeploy') def lambda_handler(event, context): appName = event['ResourceProperties']['appName'] deploymentGroup = event['ResourceProperties']['deploymentGroup'] autoScalingGroups = event['ResourceProperties']['autoScalingGroups'] serviceRoleArn = event['ResourceProperties']['serviceRoleArn'] deploymentConfigName = event['ResourceProperties']['deploymentConfigName'] print('REQUEST RECEIVED:\n' + json.dumps(event)) responseData = {} try: res = client.get_application(applicationName=appName) if res['ResponseMetadata']['HTTPStatusCode'] == 200: res = client.delete_application( applicationName=appName ) logger.info(res) logger.info("SUCCESS: Existed CodeDeploy Application deleted.") except: pass try: res = client.create_application( applicationName=appName, computePlatform='Server' ) logger.info(res) logger.info("SUCCESS: CodeDeploy Application created.") res = client.create_deployment_group( applicationName=appName, deploymentGroupName=deploymentGroup, autoScalingGroups=[ autoScalingGroups, ], deploymentConfigName=deploymentConfigName, serviceRoleArn=serviceRoleArn, autoRollbackConfiguration={ 'enabled': True, 'events': [ 'DEPLOYMENT_FAILURE', ] }, deploymentStyle={ 'deploymentType': 'BLUE_GREEN', 'deploymentOption': 'WITH_TRAFFIC_CONTROL' }, blueGreenDeploymentConfiguration={ 'terminateBlueInstancesOnDeploymentSuccess': { 'action': 'TERMINATE', 'terminationWaitTimeInMinutes': 15 }, 'deploymentReadyOption': { 'actionOnTimeout': 'STOP_DEPLOYMENT', 'waitTimeInMinutes': 5 }, 'greenFleetProvisioningOption': { 'action': 'COPY_AUTO_SCALING_GROUP' } }, loadBalancerInfo={ 'targetGroupInfoList': [ { 'name': event['ResourceProperties']['TargetGroup'] }, ] }, ) except ClientError as e: logger.error("ERROR: Something error!") logger.error(e) responseData = {'error': str(e)} cfnresponse.send(event, context, cfnresponse.FAILED, responseData) else: logger.info(res) logger.info( "SUCCESS: CodeDeploy Application and DeploymentGroup created.") responseData = {'success': str(res)} return cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData) Handler: index.lambda_handler Role: !GetAtt LambdaRole.Arn Runtime: python3.8 Timeout: 10 # ------------------------------------------------------------# # Custom Resource # ------------------------------------------------------------# CreateCodeDeploy: Type: Custom::CreateCodeDeploy DependsOn: - AutoScalingGroup Properties: ServiceToken: !GetAtt LambdaFunction.Arn Region: !Ref AWS::Region autoScalingGroups: !Sub '${ProjectName}-asg' deploymentConfigName: !Ref DeploymentConfigName serviceRoleArn: !GetAtt CodeDeployRole.Arn TargetGroup: !Sub '${ProjectName}-${TargetGroupName}' appName: !Sub '${ProjectName}-${CodeDeployAppName}' deploymentGroup: !Sub '${ProjectName}-${CodeDeployDeploymentGroupName}' # ------------------------------------------------------------# # IAMRole For CustomResource Lambda # ------------------------------------------------------------# LambdaRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole - arn:aws:iam::aws:policy/AWSCodeDeployFullAccess LambdaPolicy: Type: AWS::IAM::Policy Properties: PolicyName: LambdaPolicy PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - ec2:* - logs:* Resource: '*' - Effect: Allow Resource: '*' Action: - iam:PassRole Condition: StringEqualsIfExists: iam:PassedToService: - codedeploy.amazonaws.com Roles: - !Ref LambdaRole # ------------------------------------------------------------# # Alb Log Bucket # ------------------------------------------------------------# LogsBacket: Type: AWS::S3::Bucket Properties: BucketName: !Sub alb-log-${AWS::AccountId} AccessControl: LogDeliveryWrite BucketEncryption: ServerSideEncryptionConfiguration: - ServerSideEncryptionByDefault: SSEAlgorithm: AES256 PublicAccessBlockConfiguration: BlockPublicAcls: TRUE BlockPublicPolicy: TRUE IgnorePublicAcls: TRUE RestrictPublicBuckets: TRUE LifecycleConfiguration: Rules: - Id: 'Delete-After-400days' Status: Enabled ExpirationInDays: 400 LogsBucketPolicy: Type: AWS::S3::BucketPolicy DependsOn: LogsBacket Properties: Bucket: !Sub alb-log-${AWS::AccountId} PolicyDocument: Statement: - Action: - 's3:PutObject' Effect: 'Allow' Resource: - Fn::Join: - '' - - 'arn:aws:s3:::' - !Sub 'alb-log-${AWS::AccountId}' - '/*' Principal: AWS: '582318560864' - Action: - 's3:PutObject' Effect: 'Allow' Resource: - Fn::Join: - '' - - 'arn:aws:s3:::' - !Sub 'alb-log-${AWS::AccountId}' - '/*' Principal: Service: 'delivery.logs.amazonaws.com' Condition: StringEquals: 's3:x-amz-acl': - 'bucket-owner-full-control' - Action: - 's3:GetBucketAcl' Effect: 'Allow' Resource: Fn::Join: - '' - - 'arn:aws:s3:::' - !Sub 'alb-log-${AWS::AccountId}' Principal: Service: 'delivery.logs.amazonaws.com' # CodePipeline S3 Artifact Bucket ArtifactBucket: Type: AWS::S3::Bucket Properties: PublicAccessBlockConfiguration: BlockPublicAcls: True BlockPublicPolicy: True IgnorePublicAcls: True RestrictPublicBuckets: True # CloudWatchEventの実行ルール AmazonCloudWatchEventRule: Type: AWS::Events::Rule Properties: EventPattern: source: - aws.codecommit detail-type: - CodeCommit Repository State Change resources: - Fn::Join: - '' - - 'arn:aws:codecommit:' - !Ref 'AWS::Region' - ':' - !Ref 'AWS::AccountId' - ':' - !Ref CodeCommitRepositoryName detail: event: - referenceCreated - referenceUpdated referenceType: - branch referenceName: - !Ref BranchName Targets: - Arn: !Sub arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:${Pipeline} RoleArn: !GetAtt CloudwatchEventRole.Arn Id: codepipeline-AppPipeline # CodeBuild CodeBuildProject: Type: AWS::CodeBuild::Project Properties: ServiceRole: !Ref CodeBuildServiceRole Artifacts: Type: CODEPIPELINE Source: Type: CODEPIPELINE BuildSpec: | version: 0.2 phases: install: commands: - echo Entered the install phase... - echo Please Something to do in the install phase... pre_build: commands: - echo Entered the pre_build phase... - echo Please Something to do in the pre_build phase... build: commands: - echo Entered the build phase... - echo Build started on `date` - echo Please Something to do in the build phase... post_build: commands: - echo Build completed on `date` - echo Please Something to do in the post_build phase... artifacts: files: - '**/*' Environment: PrivilegedMode: true ComputeType: BUILD_GENERAL1_SMALL Image: aws/codebuild/standard:4.0 Type: LINUX_CONTAINER EnvironmentVariables: - Name: DOCKER_BUILDKIT Value: '1' Name: !Ref AWS::StackName # ------------------------------------------------------------# # CodePipeline # ------------------------------------------------------------# Pipeline: Type: AWS::CodePipeline::Pipeline Properties: RoleArn: !GetAtt CodePipelineServiceRole.Arn Name: !Sub ${ProjectName}-${CodeDeployAppName}-pipeline ArtifactStore: Type: S3 Location: !Ref ArtifactBucket Stages: - Name: Source Actions: - Name: SourceAction ActionTypeId: Category: Source Owner: AWS Version: '1' Provider: CodeCommit Configuration: RepositoryName: !Ref CodeCommitRepositoryName PollForSourceChanges: false BranchName: !Ref BranchName RunOrder: 1 OutputArtifacts: - Name: App - Name: Build Actions: - Name: Build ActionTypeId: Category: Build Owner: AWS Version: '1' Provider: CodeBuild Configuration: ProjectName: !Ref CodeBuildProject RunOrder: 1 InputArtifacts: - Name: App OutputArtifacts: - Name: BuildOutput # - Name: Approval # Actions: # - Name: Manual_Approval # ActionTypeId: # Category: Approval # Owner: AWS # Version: '1' # Provider: Manual # Configuration: # CustomData: !Sub '${CodeDeployAppName} will be updated. Do you want to deploy it?' # NotificationArn: !Ref NotificationArn - Name: Deploy Actions: - Name: Deploy ActionTypeId: Category: Deploy Owner: AWS Version: '1' Provider: CodeDeploy Configuration: ApplicationName: !Sub '${ProjectName}-${CodeDeployAppName}' DeploymentGroupName: !Sub '${ProjectName}-${CodeDeployDeploymentGroupName}' RunOrder: 1 InputArtifacts: - Name: App Region: !Ref AWS::Region # ------------------------------------------------------------# # Outputs # ------------------------------------------------------------# Outputs: InternetAlbUrl: Description: URL of the alb Value: !Join ['', ['http://', !GetAtt InternetAlb.DNSName]] PipelinelogicalID: Description: logical ID. Value: !Ref Pipeline
さいごに
なかなかEC2のB/Gデプロイをする機会もなくなってきているかと思いますが、利用する方がいましたら参考になれば幸いです。